﻿#include "pch.h"
#include "qgp/qgach.h"
#include "qg_blits.h"

#ifndef QG_NO_PNG

#if _SB_WINDOWS_ || _SB_ANDROID_
#	include "png/png.h"
#	ifdef _MSC_VER
#		if _SB_64_
#			if _SB_DEBUG_
#				pragma comment(lib, "png64_debug.lib")
#			else
#				pragma comment(lib, "png64.lib")
#			endif
#		else
#			if _SB_DEBUG_
#				pragma comment(lib, "png_debug.lib")
#			else
#				pragma comment(lib, "png.lib")
#			endif
#		endif
#	endif
#else
#	include <png.h>
#endif

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4611)		// '_setjmp' 및 C++ 개체 소멸 사이의 상호 작용이 이식 가능하지 않습니다.
#endif

namespace _img
{
	//
	class PngBase : public tpl::MemAlloc<>
	{
	public:
		static void ExceptError(png_structp ptr, png_const_charp msg)
		{
			k_tracef(KLV_ERROR, "qg.png", msg);
			longjmp(png_jmpbuf(ptr), 1);
		}

		static void ExceptWarn(png_structp ptr, png_const_charp msg)
		{
			k_tracef(KLV_WARNING, "qg.png", msg);
		}
	};

	//
	class ImageReaderPng : public qbFsReader, public PngBase
	{
	public:
		struct PngBuffer
		{
			const kbyte*	data;
			png_size_t		size;
			png_size_t		loc;

			PngBuffer(kconstpointer data, png_size_t size)
				: data((const kbyte*)data), size(size), loc(0)
			{
			}
		};

	public:
		K_OVR void Dispose()
		{
			delete this;
		}

		K_OVR bool Test(kconstpointer data, kint size)
		{
			return !png_sig_cmp((png_bytep)data, 0, 8);
		}

		K_OVR bool Read(kObj* ctnr, kconstpointer data, kint size, kint mask)
		{
			// 이미지 검사
			if (png_sig_cmp((png_bytep)data, 0, 8))
				return false;

			png_structp pptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, ExceptError, ExceptWarn);

			if (!pptr)
				return false;

			png_infop pinfo = png_create_info_struct(pptr);

			if (!pinfo)
			{
				png_destroy_read_struct(&pptr, NULL, NULL);
				return false;
			}

			if (setjmp(png_jmpbuf(pptr)))
			{
				png_destroy_read_struct(&pptr, &pinfo, NULL);

				return false;
			}

			PngBuffer fio(data, size);
			png_set_read_fn(pptr, &fio, ReadBuffer);

			// 아래 놈 때문에 다 죽어!!!
			//png_set_sig_bytes(pptr, 8);

			// 설정 맞추기
			png_uint_32 width, height;
			kint depth, cct, i;

			png_read_info(pptr, pinfo);
			png_get_IHDR(pptr, pinfo, &width, &height, &depth, &cct, NULL, NULL, NULL);

			if (cct == PNG_COLOR_TYPE_PALETTE)
				png_set_palette_to_rgb(pptr);

			if (depth < 8)
			{
				if (cct == PNG_COLOR_TYPE_GRAY || cct == PNG_COLOR_TYPE_GRAY_ALPHA)
					png_set_expand_gray_1_2_4_to_8(pptr);
				else
					png_set_packing(pptr);
			}

			if (png_get_valid(pptr, pinfo, PNG_INFO_tRNS))
				png_set_tRNS_to_alpha(pptr);

			if (depth == 16)
				png_set_strip_16(pptr);

			if (cct == PNG_COLOR_TYPE_GRAY || cct == PNG_COLOR_TYPE_GRAY_ALPHA)
				png_set_gray_to_rgb(pptr);

			if (png_get_sRGB(pptr, pinfo, &i))
				png_set_gamma(pptr, 2.2, 0.45455);
			else
			{
				double igm;

				if (png_get_gAMA(pptr, pinfo, &igm))
					png_set_gamma(pptr, 2.2, igm);
				else
					png_set_gamma(pptr, 2.2, 0.45455);
			}

			// 24비트 컬러가 없기 땜시 무조건 32비트
			if (!(cct&PNG_COLOR_MASK_ALPHA))
				png_set_add_alpha(pptr, 0xFF, PNG_FILLER_AFTER);

			png_read_update_info(pptr, pinfo);
			png_get_IHDR(pptr, pinfo, &width, &height, &depth, &cct, NULL, NULL, NULL);

			if (cct == PNG_COLOR_TYPE_RGB_ALPHA)
			{
#if _SB_BIGENDIAN_
				png_set_swap_alpha(pptr);

				// swap_alpha는 맞는데 bgr은 이게 맞나 몰것다
				if (K_OFLAG(mask, QGRESF_BGRA))
					png_set_bgr(pptr);
#else
				if ((K_OFLAG(mask, QGRESF_BGRA)) == 0)
					png_set_bgr(pptr);
#endif
			}

			// 컨테이너 검사
			qgImage* img = kCrbs::CastLinear<qgImage>(ctnr);

			if (img == NULL)
			{
				// 이거 이미지가 아님
				return false;
			}

			// 본격 이미지에 넣기
			img->Initialize((kint)width, (kint)height, (K_OFLAG(mask, QGRESF_BGRA)) ? QGCF32_BGRA : QGCF32_RGBA);

			kbyte** rowptrs = k_new(height, png_bytep);
			kbyte* buf = (kbyte*)img->GetData();

			for (i = 0; i < (kint)height; i++)
			{
				rowptrs[i] = buf;
				buf += img->GetPitch();
			}

			if (setjmp(png_jmpbuf(pptr)))
			{
				png_destroy_read_struct(&pptr, &pinfo, NULL);
				k_delete(rowptrs);
				img->Dispose();

				return false;
			}

			png_read_image(pptr, rowptrs);
			png_read_end(pptr, NULL);

			//
			k_delete(rowptrs);
			png_destroy_read_struct(&pptr, &pinfo, NULL);

			return true;
		}

		static void PNGAPI ReadBuffer(png_structp ptr, png_bytep data, png_size_t len)
		{
			PngBuffer* f = (PngBuffer*)png_get_io_ptr(ptr);

			if (f->loc + len > f->size)
				png_error(ptr, "read check error");
			else
			{
				memcpy(data, f->data + f->loc, len);
				f->loc += len;
			}
		}
	};

	//
	class ImageWriterPng : public qbFsWriter, public PngBase
	{
	public:
		K_OVR void Dispose()
		{
			delete this;
		}

		K_OVR bool Test(const char* ext)
		{
			return k_strieqv(ext, "png") != 0;
		}

		K_OVR bool Write(kStream* st, kObj* ctnr, kint mask)
		{
			// 컨테이너 검사
			qgImage* img = kCrbs::CastLinear<qgImage>(ctnr);

			if (img == NULL)
			{
				// 이거 이미지가 아님
				return false;
			}

			// 구성 검사
			const qbPixelProp& prop = img->GetPixelProp();

			if (prop.fmt != QGCF32_RGB && prop.fmt != QGCF32_RGBA &&
				prop.fmt != QGCF32_BGR && prop.fmt != QGCF32_BGRA)
				return false;

			kint pitch = img->GetPitch();
			kint width = img->GetWidth();
			kint height = img->GetHeight();

			char username[K_MAX_PATH];
			k_getusername(username, K_MAX_PATH - 1);

			//
			png_structp pptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, ExceptError, ExceptWarn);

			if (!pptr)
				return false;

			png_infop pinfo = png_create_info_struct(pptr);

			if (!pinfo)
			{
				png_destroy_read_struct(&pptr, NULL, NULL);
				return false;
			}

			if (setjmp(png_jmpbuf(pptr)))
			{
				png_destroy_write_struct(&pptr, &pinfo);
				return false;
			}

			png_set_write_fn(pptr, st, WriteFile, NULL);

			//
			kbyte* data = (kbyte*)img->GetData();
			kbyte** rowptrs = k_new(height, kbyte*);

			for (kint i = 0; i < height; i++)
				rowptrs[i] = data + (pitch*i);

			if (setjmp(png_jmpbuf(pptr)))
			{
				png_destroy_write_struct(&pptr, &pinfo);
				k_delete(rowptrs);
				return false;
			}

			// 만든 소프트웨어, 저자명 붙임
			png_text ptxt[3];
			ptxt[0].compression = PNG_TEXT_COMPRESSION_NONE;
			ptxt[0].key = (png_charp)"Software";
			ptxt[0].text = (png_charp)"SBQS Version 1.2";
			ptxt[1].compression = PNG_TEXT_COMPRESSION_NONE;
			ptxt[1].key = (png_charp)"Author";
			ptxt[1].text = username;
			png_set_text(pptr, pinfo, ptxt, 2);

			// 72 DPI으로 설정
			int ppm = (72 * 10000 + 127) / 254;
			png_set_pHYs(pptr, pinfo, ppm, ppm, PNG_RESOLUTION_METER);

			//
			png_set_IHDR(pptr, pinfo, width, height, 8,
				(prop.flags&QGCFF_ALPHA) ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB,
				PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);

			png_set_rows(pptr, pinfo, rowptrs);
			png_write_png(pptr, pinfo,
				(prop.flags&QGCFF_BGR) ? PNG_TRANSFORM_IDENTITY : PNG_TRANSFORM_BGR, NULL);

			png_destroy_write_struct(&pptr, &pinfo);
			k_delete(rowptrs);

			return true;
		}

		static void PNGAPI WriteFile(png_structp ptr, png_bytep data, png_size_t len)
		{
			png_size_t check;

			kStream* st = (kStream*)png_get_io_ptr(ptr);
			check = (png_size_t)st->Write(data, 0, (kint)len);

			if (check != len)
				png_error(ptr, "write check error");
		}
	};

	//
	qbFsReader* CreateReaderImagePng()
	{
		return new ImageReaderPng();
	}

	//
	qbFsWriter* CreateWriterImagePng()
	{
		return new ImageWriterPng();
	}
}

#ifdef _MSC_VER
#pragma warning(pop)
#endif


#endif	// QG_NO_PNG
